package com.eedi.framework.config.service;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.google.common.collect.ImmutableList;
import com.eedi.framework.common.config.CfgConstant;
import com.eedi.framework.common.config.ConfigMap;
import com.eedi.framework.config.dal.dataobject.ConfigDefineDO;
import com.eedi.framework.config.dal.dataobject.ConfigValueDO;
import com.eedi.framework.config.dal.mapper.SysConfigValueMapper;
import com.eedi.framework.config.dal.mapper.SysConfigDefineMapper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * @author zybourn
 * @version 1.0
 * @description
 * @since 2024/3/13 19:15
 **/
@Service
@Validated
@CacheConfig(cacheNames = "config_value")
@RequiredArgsConstructor
@Slf4j
public class SysConfigValueServiceImpl implements SysConfigValueService {
    /**
     * 默认缓存超时时间(秒)
     */
    private static final int MAX_DEFAULT_EXPIRE_SECONDS = 3600;
    private static final int MIN_DEFAULT_EXPIRE_SECONDS = 1800;
    @Resource
    private SysConfigValueMapper sysConfigValueMapper;
    @Resource
    private SysConfigDefineMapper sysConfigDefineMapper;

    private static final String DEFAULT = "DEFAULT";

    private final RedisTemplate<String, Object> strObjRedisTemplate;


    @Override
    public <T> T get(CfgConstant<T> cfgConstant,String bizKey) {
        return getConfigValue(cfgConstant, bizKey);
    }

    @Override
    public ConfigMap getList(String bizKey, CfgConstant<?>... cfgConstants) {
        if (cfgConstants.length == 1) {
            return ConfigMap.of(cfgConstants[0], get(cfgConstants[0], bizKey));
        }
        final List<CfgConstant<?>> configList = Arrays.stream(cfgConstants).collect(Collectors.toList());
        ConfigMap result = new ConfigMap();
        final List<String> keyList = configList.stream()
                .map(cfgConstant -> cacheKey(cfgConstant.getCfgCode(), bizKey)).collect(Collectors.toList());
        final List<Object> cacheValueList = strObjRedisTemplate.opsForValue().multiGet(keyList);
        List<CfgConstant<?>> leftKeys = new ArrayList<>();
        for (int i = 0, unSysListSize = configList.size(); i < unSysListSize; i++) {
            CfgConstant<?> cfgConstant = configList.get(i);
            final Object cacheValue = cacheValueList.get(i);
            if (cacheValue == null) {
                leftKeys.add(cfgConstant);
            } else {
                result.put(cfgConstant, paresCacheValue(cfgConstant, cacheValue));
            }
        }
        if (!leftKeys.isEmpty()) {
            ConfigMap dbValuesMap = mgetFromDB(bizKey, leftKeys);
            result.putAll(dbValuesMap);
        }
        return result;
    }


    /**
     * 更新配置
     * 不支持列表类型
     */
    @Override
    public void batchUpdate(String bizKey, ConfigMap cfgMap){
        //校验参数
        for (CfgConstant<?> cfgConstant : cfgMap.keySet()) {
            if(List.class == cfgConstant.getJavaType()){
                throw new UnsupportedOperationException("不支持列表配置:"+cfgConstant);
            }
        }
        final List<String> codeList = cfgMap.keySet().stream().map(CfgConstant::getCfgCode).collect(Collectors.toList());
        List<ConfigValueDO> oldValueList = sysConfigValueMapper.getValueByCodeList(codeList, bizKey);
        Map<String, ConfigValueDO> oldValueMap = oldValueList.stream().collect(Collectors.toMap(ConfigValueDO::getCfgCode, m -> m));
        Map<CfgConstant, String> newValueMap = cfgMap.entrySet().stream()
                .filter(entry -> entry.getValue() != null)
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        entry -> entry.getValue().toString())
                );
        for (Map.Entry<CfgConstant, String> entry : newValueMap.entrySet()) {
            CfgConstant key = entry.getKey();
            String newValue = entry.getValue();
            if(oldValueMap.get(key.getCfgCode()) == null){
                ConfigValueDO configValue = new ConfigValueDO();
                configValue.setCfgValue1(newValue);
                configValue.setBizKey(bizKey);
                configValue.setCfgCode(key.getCfgCode());
                configValue.setCfgType(key.getCfgType());
                sysConfigValueMapper.insert(configValue);
            } else if (newValue != null && !newValue.equals(oldValueMap.get(key.getCfgCode()).getCfgValue1())) {
                ConfigValueDO configValue = oldValueMap.get(key.getCfgCode());
                sysConfigValueMapper.update(null, new UpdateWrapper<ConfigValueDO>().lambda()
                        .set(ConfigValueDO::getCfgValue1, newValue)
                        .eq(ConfigValueDO::getConfigValueId, configValue.getConfigValueId()));
            }
            //删除cfgManager缓存 使用的地方会从数据库重新获取(整个过程无事物,如果有事物,清理方法最好放到事物提交之后)
            strObjRedisTemplate.delete(cacheKey(key.getCfgCode(),bizKey));
        }
    }

    @Override
    public void addValue(CfgConstant cfgCodeEnum, String bizKey, String value) {
        addValue(cfgCodeEnum.getCfgType(), cfgCodeEnum.getCfgCode(), bizKey, value);
    }

    /**
     * 更新配置，不支持列表
     * @param cfgConstant
     * @param bizKey
     * @param value
     */
    @Override
    public void updateValue(CfgConstant cfgConstant, String bizKey, String value) {
        ConfigValueDO configValueDO = new ConfigValueDO();
        configValueDO.setCfgCode(cfgConstant.getCfgCode());
        configValueDO.setCfgType(cfgConstant.getCfgType());
        configValueDO.setBizKey(bizKey);
        configValueDO.setCfgValue1(value);
        ConfigValueDO existConfigValue = sysConfigValueMapper.getValueByCode(cfgConstant.getCfgCode(), bizKey);
        if (Objects.isNull(existConfigValue)) {
            sysConfigValueMapper.insert(configValueDO);
        } else {
            sysConfigValueMapper.update(null, new UpdateWrapper<ConfigValueDO>().lambda()
                    .set(ConfigValueDO::getCfgValue1, configValueDO.getCfgValue1())
                    .eq(ConfigValueDO::getConfigValueId, existConfigValue.getConfigValueId()));
            strObjRedisTemplate.delete(cacheKey(cfgConstant.getCfgCode(),bizKey));
        }

    }
    @Override
    public void deleteValue(CfgConstant cfgConstant, String bizKey) {
        sysConfigValueMapper.delByCode(cfgConstant.getCfgType(), cfgConstant.getCfgCode(), bizKey);
        strObjRedisTemplate.delete(cacheKey(cfgConstant.getCfgCode(),bizKey));
    }

    @Override
    public void batchDel(String bizKey, CfgConstant<?>... cfgConstants) {
        final List<CfgConstant<?>> configList = Arrays.stream(cfgConstants).collect(Collectors.toList());
        configList.forEach(config-> deleteValue(config,bizKey));
    }

    @Override
    public Map<String, Object> getConfigMap(String bizKey, CfgConstant<?>... cfgConstants) {
        final ConfigMap mget = getList(bizKey, cfgConstants);
        return mget.getCamelCaseMap();
    }

    private <T> T getConfigValue(CfgConstant<T> cfgConstant,String bizKey) {
        //先从缓存取
        final String cacheKey = cacheKey(cfgConstant.getCfgCode(), bizKey);
        T cacheValue = getFromCache(cfgConstant, cacheKey);
        if (cacheValue != null || strObjRedisTemplate.hasKey(cacheKey)) {
            return cacheValue;
        }
        final List<ConfigValueDO> valueList = sysConfigValueMapper.getValueByCodeIncludeList(cfgConstant.getCfgCode(), bizKey);
        return getT(cfgConstant, valueList, bizKey);
    }

    private <T> T getT(CfgConstant<T> cfgConstant, List<ConfigValueDO> configValues, String bizId) {
        T result;
        if (CollectionUtils.isEmpty(configValues)) {
            return getDefaultValue(cfgConstant);
        }
        final List<String> valueList = configValues.stream().map(ConfigValueDO::getCfgValue1).collect(Collectors.toList());
        if (cfgConstant.getJavaType() == List.class) {
            result = (T) valueList.stream().map(value -> convert(value, cfgConstant.getSubType()))
                    .collect(Collectors.toList());
        } else {
            if (valueList.size() > 1) {
                log.error("配置单属性值返回了多个结果,请检查配置:" + cfgConstant.getCfgCode());
            }
            result = (T) convert(valueList.get(0), cfgConstant.getJavaType());
        }
        int randomInt = RandomUtil.randomInt(MIN_DEFAULT_EXPIRE_SECONDS,MAX_DEFAULT_EXPIRE_SECONDS);
        strObjRedisTemplate.opsForValue().set(cacheKey(cfgConstant.getCfgCode(), bizId), result, randomInt, TimeUnit.SECONDS);

        return result;
    }

    /**
     * 取默认值
     */
    @SuppressWarnings("unchecked")
    private <T> T getDefaultValue(CfgConstant<T> cfgConstant) {
        Object defaultValue;
        final ConfigDefineDO define = sysConfigDefineMapper.getByCode(cfgConstant.getCfgCode());
        final Class<?> javaType = cfgConstant.getJavaType();
        final Class<?> subType = cfgConstant.getSubType();
        if (javaType == List.class) {
            final Object element = convert(define.getCfgDefaultValue1(), subType);
            defaultValue = element != null ? ImmutableList.of(element) : Collections.emptyList();
        } else {
            defaultValue = convert(define.getCfgDefaultValue1(), javaType);
        }
        return (T) defaultValue;
    }


    public void addValue(String cfgType, String cfgCode, String bizKey, String value) {
        //不做任何空的判断，就算空的就插入
        final ConfigValueDO configValueDO = new ConfigValueDO();
        configValueDO.setCfgType(StrUtil.trimToEmpty(cfgType));
        configValueDO.setCfgCode(StrUtil.trimToEmpty(cfgCode));
        configValueDO.setBizKey(StrUtil.trimToEmpty(bizKey));
        configValueDO.setCfgValue1(StrUtil.trimToEmpty(value));
        sysConfigValueMapper.insert(configValueDO);
    }

    @SneakyThrows
    private ConfigMap mgetFromDB(String bizKey, List<CfgConstant<?>> cfgList) {
        ConfigMap result = new ConfigMap();
        final List<String> codeList = cfgList.stream().map(CfgConstant::getCfgCode).collect(Collectors.toList());
        final List<ConfigValueDO> configValueList = sysConfigValueMapper.getValueByCodeList(codeList, bizKey);
        final Map<String, List<ConfigValueDO>> codeGroupBy = configValueList.stream()
                .collect(Collectors.groupingBy(ConfigValueDO::getCfgCode));
        //数据库里也没有的
        List<CfgConstant<?>> leftKeys = new ArrayList<>();
        for (CfgConstant<?> key : cfgList) {
            String code = key.getCfgCode();
            List<ConfigValueDO> configValues = codeGroupBy.get(code);
            if (CollectionUtils.isEmpty(configValues)) {
                leftKeys.add(key);
            }else {
                final Object value = getT(key, configValues, bizKey);
                result.put(key, value);
            }
        }
        //批量拉取默认值缓存
        if (!leftKeys.isEmpty()) {
            //系统配置的biz_id=''
            ConfigMap defaultValuesMap = mgetDefault(leftKeys);
            result.putAll(defaultValuesMap);
        }
        return result;
    }

    private ConfigMap mgetDefault(List<CfgConstant<?>> cfgList){
        ConfigMap result = new ConfigMap();
        final List<String> keyList = cfgList.stream()
                .map(cfgConstant -> cacheKey(cfgConstant.getCfgCode(), DEFAULT)).collect(Collectors.toList());
        final List<Object> cacheValueList = strObjRedisTemplate.opsForValue().multiGet(keyList);
        for (int i = 0, sysCfgListSize = cfgList.size(); i < sysCfgListSize; i++) {
            CfgConstant<?> cfgConstant = cfgList.get(i);
            final Object cacheValue = cacheValueList.get(i);
            if (cacheValue == null && !strObjRedisTemplate.hasKey(cacheKey(cfgConstant.getCfgCode(), DEFAULT))) {
                result.put(cfgConstant, getDefaultValue(cfgConstant));
            } else {
                result.put(cfgConstant, paresCacheValue(cfgConstant, cacheValue));
            }
        }
        return result;
    }


    @NotNull
    private static String cacheKey(@NotNull String code, @NotNull String bizKey) {
        return "config_value:" + code + ":" + bizKey;
    }

    /**
     * 从缓存获取值
     */
    @SuppressWarnings("unchecked")
    private <T> T getFromCache(CfgConstant<T> cfgConstant, String key){
        Object cacheValue = strObjRedisTemplate.opsForValue().get(key);
        return paresCacheValue(cfgConstant, cacheValue);
    }

    /**
     * 方法主要是处理反序列化后类型不一致问题
     */
    @SuppressWarnings("unchecked")
    private static <T> T paresCacheValue(CfgConstant<T> cfgConstant, Object cacheValue){
        if(cacheValue == null){
            return null;
        }
        if(cacheValue instanceof List){
            List<Object> listValue = (List<Object>)cacheValue;
            if (CollectionUtils.isNotEmpty(listValue) && listValue.get(0).getClass() != cfgConstant.getSubType()) {
                return (T)listValue.stream().map(item ->convert(String.valueOf(item), cfgConstant.getJavaType())).collect(Collectors.toList());
            }
            return (T)cacheValue;
        }
        if(cfgConstant.getJavaType() != cacheValue.getClass()){
            return (T) convert(String.valueOf(cacheValue), cfgConstant.getJavaType());

        }
        return (T)cacheValue;
    }


    private static <T> T convert(String val, Class<T> clz) {
        if (StrUtil.isBlank(val)) {
            return null;
        }
        if (clz == Integer.class) {
            return (T) Integer.valueOf(val);
        } else if (clz == Long.class) {
            return (T) Long.valueOf(val);
        } else if (clz == Double.class) {
            return (T) Double.valueOf(val);
        } else if (clz == Float.class) {
            return (T) Float.valueOf(val);
        } else if (clz == Boolean.class) {
            return (T) Boolean.valueOf(val);
        } else {
            return (T) val;
        }
    }
}
